// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------

package injector

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	log "github.com/Sirupsen/logrus"
	"k8s.io/api/admission/v1beta1"
	corev1 "k8s.io/api/core/v1"
)

const (
	sidecarContainerName  = "daprd"
	daprEnabledKey        = "dapr.io/enabled"
	daprPortKey           = "dapr.io/port"
	daprConfigKey         = "dapr.io/config"
	daprProtocolKey       = "dapr.io/protocol"
	daprIDKey             = "dapr.io/id"
	daprProfilingKey      = "dapr.io/profiling"
	daprLogLevel          = "dapr.io/log-level"
	daprMaxConcurrencyKey = "dapr.io/max-concurrency"
	sidecarHTTPPort       = 3500
	sidecarGRPCPORT       = 50001
	apiAddress            = "http://dapr-api"
	placementService      = "dapr-placement"
	sidecarHTTPPortName   = "dapr-http"
	sidecarGRPCPortName   = "dapr-grpc"
	defaultLogLevel       = "info"
	kubernetesMountPath   = "/var/run/secrets/kubernetes.io/serviceaccount"
)

func (i *injector) getPodPatchOperations(ar *v1beta1.AdmissionReview, namespace, image string) ([]PatchOperation, error) {
	req := ar.Request
	var pod corev1.Pod
	if err := json.Unmarshal(req.Object.Raw, &pod); err != nil {
		log.Errorf("could not unmarshal raw object: %v", err)
		return nil, err
	}

	log.Infof(
		"AdmissionReview for Kind=%v, Namespace=%v Name=%v (%v) UID=%v "+
			"patchOperation=%v UserInfo=%v",
		req.Kind,
		req.Namespace,
		req.Name,
		pod.Name,
		req.UID,
		req.Operation,
		req.UserInfo,
	)

	if !isResourceDaprEnabled(pod.Annotations) || podContainsSidecarContainer(&pod) {
		return nil, nil
	}

	appPort, err := getAppPort(pod.Annotations)
	if err != nil {
		return nil, err
	}
	config := getConfig(pod.Annotations)
	protocol := getProtocol(pod.Annotations)
	id := getAppID(pod)
	enableProfiling := profilingEnabled(pod.Annotations)
	placementAddress := fmt.Sprintf("%s:80", getKubernetesDNS(placementService, namespace))
	apiSrvAddress := getKubernetesDNS(apiAddress, namespace)
	logLevel := getLogLevel(pod.Annotations)
	maxConcurrency, err := getMaxConcurrency(pod.Annotations)
	if err != nil {
		log.Warn(err)
	}

	appPortStr := ""
	if appPort > 0 {
		appPortStr = fmt.Sprintf("%v", appPort)
	}
	maxConcurrencyStr := fmt.Sprintf("%v", maxConcurrency)

	tokenMount := getTokenVolumeMount(pod)
	sidecarContainer := getSidecarContainer(appPortStr, protocol, id, config, image, req.Namespace, apiSrvAddress, placementAddress, strconv.FormatBool(enableProfiling), logLevel, maxConcurrencyStr, tokenMount)

	patchOps := []PatchOperation{}
	var path string
	var value interface{}
	if len(pod.Spec.Containers) == 0 {
		path = "/spec/containers"
		value = []corev1.Container{sidecarContainer}
	} else {
		path = "/spec/containers/-"
		value = sidecarContainer
	}

	patchOps = append(
		patchOps,
		PatchOperation{
			Op:    "add",
			Path:  path,
			Value: value,
		},
	)

	return patchOps, nil
}

func getTokenVolumeMount(pod corev1.Pod) *corev1.VolumeMount {
	for _, c := range pod.Spec.Containers {
		for _, v := range c.VolumeMounts {
			if v.MountPath == kubernetesMountPath {
				return &v
			}
		}
	}
	return nil
}

func podContainsSidecarContainer(pod *corev1.Pod) bool {
	for _, c := range pod.Spec.Containers {
		if c.Name == sidecarContainerName {
			return true
		}
	}
	return false
}

func getMaxConcurrency(annotations map[string]string) (int32, error) {
	m, ok := annotations[daprMaxConcurrencyKey]
	if !ok {
		return -1, nil
	}
	maxConcurrency, err := strconv.ParseInt(m, 10, 32)
	if err != nil {
		return -1, fmt.Errorf("error parsing max concurrency int value %s: %s", m, err)
	}
	return int32(maxConcurrency), nil
}

func getAppPort(annotations map[string]string) (int32, error) {
	p, ok := annotations[daprPortKey]
	if !ok {
		return -1, nil
	}
	port, err := strconv.ParseInt(p, 10, 32)
	if err != nil {
		return -1, fmt.Errorf("error parsing port int value %s: %s", p, err)
	}
	return int32(port), nil
}

func getConfig(annotations map[string]string) string {
	return annotations[daprConfigKey]
}

func getProtocol(annotations map[string]string) string {
	if val, ok := annotations[daprProtocolKey]; ok && val != "" {
		return val
	}
	return "http"
}

func getAppID(pod corev1.Pod) string {
	if val, ok := pod.Annotations[daprIDKey]; ok && val != "" {
		return val
	}
	return pod.GetName()
}

func getLogLevel(annotations map[string]string) string {
	if val, ok := annotations[daprLogLevel]; ok && val != "" {
		return val
	}
	return defaultLogLevel
}

func profilingEnabled(annotations map[string]string) bool {
	enabled, ok := annotations[daprProfilingKey]
	if !ok {
		return false
	}
	switch strings.ToLower(enabled) {
	case "y", "yes", "true", "on", "1":
		return true
	default:
		return false
	}
}

func isResourceDaprEnabled(annotations map[string]string) bool {
	enabled, ok := annotations[daprEnabledKey]
	if !ok {
		return false
	}
	switch strings.ToLower(enabled) {
	case "y", "yes", "true", "on", "1":
		return true
	default:
		return false
	}
}

func getKubernetesDNS(name, namespace string) string {
	return fmt.Sprintf("%s.%s.svc.cluster.local", name, namespace)
}

func getSidecarContainer(applicationPort, applicationProtocol, id, config, daprSidecarImage, namespace, controlPlaneAddress, placementServiceAddress, enableProfiling, logLevel, maxConcurrency string, tokenVolumeMount *corev1.VolumeMount) corev1.Container {
	c := corev1.Container{
		Name:            sidecarContainerName,
		Image:           daprSidecarImage,
		ImagePullPolicy: corev1.PullAlways,
		Ports: []corev1.ContainerPort{
			{
				ContainerPort: int32(sidecarHTTPPort),
				Name:          sidecarHTTPPortName,
			},
			{
				ContainerPort: int32(sidecarGRPCPORT),
				Name:          sidecarGRPCPortName,
			},
		},
		Command: []string{"/daprd"},
		Env:     []corev1.EnvVar{{Name: "HOST_IP", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}}}, {Name: "NAMESPACE", Value: namespace}},
		Args:    []string{"--mode", "kubernetes", "--dapr-http-port", fmt.Sprintf("%v", sidecarHTTPPort), "--dapr-grpc-port", fmt.Sprintf("%v", sidecarGRPCPORT), "--app-port", applicationPort, "--dapr-id", id, "--control-plane-address", controlPlaneAddress, "--protocol", applicationProtocol, "--placement-address", placementServiceAddress, "--config", config, "--enable-profiling", enableProfiling, "--log-level", logLevel, "--max-concurrency", maxConcurrency},
	}

	if tokenVolumeMount != nil {
		c.VolumeMounts = []corev1.VolumeMount{
			*tokenVolumeMount,
		}
	}
	return c
}
